/*
 * Copyright 2007 Brian Watkins
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package bw.refresh;

import java.net.*;
import java.io.*;
import java.util.Date;
import bw.net.*;
import bw.os.*;
import bw.util.*;

public abstract class RefreshingDataSource implements Refreshable, Serializable
{
    static final long serialVersionUID = -5608377327984127260L;

    private long _sleepTime = 0L;
    private long _lastRefreshed = 0L;
    private int _failures = 0;
    private int _maxFailures = 3;
    private String _errorMessage = null;
    private String _id = null;
    private String _src = null;
    private String _username = null;
    private String _password = null;

    public RefreshingDataSource ( String id, String src, long sleep )
    {
        _id = id;
        _src = src;
        _sleepTime = sleep;
    }

    public RefreshingDataSource ( String id, String src, long sleep, int maxFailures )
    {
        _id = id;
        _src = src;
        _sleepTime = sleep;
        _maxFailures = maxFailures;
    }

    public void setId ( String s ) { _id = s; }
    public String getId () { return _id; }
    
    public void setSrc ( String s ) { _src = s; }
    public String getSrc () { return _src; }

    public void setUsername ( String u ) { _username = u; }
    public String getUsername () { return _username; }
    
    public void setPassword ( String u ) { _password = u; }
    public String getPassword () { return _password; }

    public void setSleepTime ( long sleep ) { _sleepTime = sleep; }
    public long getSleepTime () { return _sleepTime; }

    public Date getLastRefreshed () { return ( new Date( _lastRefreshed ) ); }
    public boolean hasRefreshed () { return ( _lastRefreshed > 0 ); }

    protected String getErrorMessage () { return _errorMessage; }

    public void handleUpdateFailure ()
    {
        //nothing. Subclasses can override.
        //called when update has failed the max times in a row.
    }

    public int getFailures () { return _failures; }

    public void beginUpdate ()
    {
        //subclasses can override if need to do something
        //before the update begins
    }

    public void finishUpdate ()
    {
        //subclasses can override if need to do something
        //when the update ends *successfully*
    }

    public void refreshComplete ()
    {
        //subclasses can override to do something when the 
        //refresh is totally complete. Should be used to
        //save state at this point. 
    }

    public abstract void updateSource ( HttpURLConnection conn ) 
        throws RefresherException;

    public void updateSource () throws RefresherException
    {
        HttpURLConnection conn = null;
        NetResource res = null;

        try
        {
            res = new NetResource( _src, _username, _password );
            res.doConditionalGET();

            conn = res.getURLConnection();
        }
        catch ( MalformedURLException ex )
        {
            throw new RefresherException( "Malformed URL: " + _src );
        }
        catch ( UnknownHostException ex )
        {
            throw new RefresherException( "Unknown Host: " + _src );
        }
        catch ( IOException ex )
        {
            throw new RefresherException( "Problem getting connection for: " + _src );
        }

        if ( conn != null )
        {
            beginUpdate();

            //only update the content if it has been modified since the last check
            if ( res.shouldGET() )
            {
                //subclasses implement this to handle the update
                //this should throw a RefresherException if any problems
                updateSource( conn );
            }

            finishUpdate();
        }
        else
        {
            RefresherException rex = null;

            if ( res != null )
            {
                //couldn't get a connection
                if ( res.getError() == NetResource.NETWORK_DOWN )
                {
                    //don't log this because we were probably waking up from sleep
                    //and the network wasn't up yet but tomcat was and made
                    //requests anyway, and we don't care about those               
                    rex = new RefresherException( "Network down" );
                    rex.setLoggable( false );
                }
                else if ( res.getError() == NetResource.HTTP_NOT_OK )
                {
                    rex = new RefresherException( "Http Error: " + res.getResponseCode() );
                }
            }
            else 
            {
                //bad url (ie couldn't even make the netResource object)
                //we should probably never get here but just in case
                rex = new RefresherException( "Bad URL: " + _src );
            }

            throw rex;
        }
    }

    public boolean shouldRefresh ()
    {
        return ( ( System.currentTimeMillis() - _lastRefreshed ) > _sleepTime );
    }

    public void refresh () 
    {
        try
        {
            if ( _failures < _maxFailures )
            {
                updateSource();
            }
            else
            {
                Log.getInstance().write( "Updating of data source (" + _src +
                                         ") has failed " + _maxFailures + " times!" );
                
                //subclasses override to do something if they need to
                handleUpdateFailure();
            }
            
            //reset the failures and the refreshTime so we can try again later. 
            //note this happens even if we've really failed the max times and
            //are aborting for this cycle. we do this to let us wait a full
            //cycle before trying again. 
            //if the update failed somehow a RefresherException should be thrown so we
            //don't get here unless we have failed max times. 
            resetUpdateCycle();
        }
        catch ( RefresherException ex )
        {
            //note by default RefresherException is loggable
            if ( ex.isLoggable() )
            {
                //note this is the main place for logging refresher exceptions.
                //should only need to log here as all refresher exceptions get caught here
                Log.getInstance().write( "Error Refreshing: " + _src, ex );
            }

            _errorMessage = ex.getMessage();

            //note by default RefresherException is recoverable
            if ( ex.isRecoverable() )
            {
                //if it's potentially recoverable (ie a network problem)
                //then we'll try multiple times before failing
                _failures++;
            }
            else
            {
                //if not recoverable (ie a problem parsing xml)
                //then fail now and wait for next cycle. 
                handleUpdateFailure();
                resetUpdateCycle();
            }
        }
        catch ( Exception ex )
        {
            Log.getInstance().write( "Problem updating data source (" + 
                                     _src + ")", ex );
        }
        finally 
        {
            refreshComplete();
        }
    }

    private void resetUpdateCycle ()
    {
        //this is called in order to set things to wait
        //for another cycle before attempting to update.
        //called after a successful update and after
        //a sequence of failures (to make us wait before
        //trying again)

        //note that we just use the system time here so it may not be
        //in the proper timezone. 
        _lastRefreshed = System.currentTimeMillis();
        _failures = 0;
    }

    public void sync ()
    {
        //Save any changes to the ObjectStore
        try
        {
            ObjectStore.getInstance().put( _id, this );
        }
        catch ( Exception ex )
        {
            Log.getInstance().write( "Problem syncing refreshingDataSource: " + _id, ex );
        }
    }

    public void destroy ()
    {
        try
        {
            ObjectStore.getInstance().remove( _id );
        }
        catch ( Exception ex )
        {
            Log.getInstance().write( "Problem removing refreshingDataSource: " + _id, ex );
        }
    }
    

}